//=============================================================================
// ProMesh.NET - .NET Web Application Framework 
//
// Copyright (c) 2003-2008 Philippe Leybaert
//
// Permission is hereby granted, free of charge, to any person obtaining a copy 
// of this software and associated documentation files (the "Software"), to deal 
// in the Software without restriction, including without limitation the rights 
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
// copies of the Software, and to permit persons to whom the Software is 
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in 
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//=============================================================================

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text.RegularExpressions;
using Activa.LazyParser;
using Activa.SharpTemplate;

namespace Activa.ProMesh
{
    internal class ProMeshTemplateConfig : SharpTemplateConfig
    {
        private static class TokenID
        {
            public const string Expression = "EXPR";
            public const string QuietExpression = "QEXPR";
            public const string Translation = "TRANSLATION";
            public const string Control = "CONTROL";
            public const string Include = "INCLUDE";
            public const string Component = "COMPONENT";
        }

        public ProMeshTemplateConfig()
        {
            /*
             * ProMesh 2.0 Tags
             * ================
             * {{tag}}
             * {{tag:formatString}}
             * ##translation##
             * ##translation(param1,param2,...)##
             * [[control]]
             * [[control:cssClass:errorClass]]
             * {{INCLUDE template}}
             * {{RENDER template}}
             * {{RENDER template, @var1=var1, @var2=var2, ... }}
             * {{COMPONENT component, @var1=var1, @var2=var2, ... }}
             * {% component, @var1=var1, @var2=var2, ... %}
             * 
             * Deprecated
             * ================
             * $[tag]
             * $[tag:formatString]
             * $[[control]]
             * $[[control:cssClass:errorClass]]
             * ${translation}
             * ${translation(param1,param2,...)}
             * $[RENDER template]
             * $[RENDER template, @var1=var1, @var2=var2]
             * $[INCLUDE template]
             */

            AddToken(TemplateTokenType.ForEach, @"<!--\$\[\s*foreach\s+(?<iterator>[A-Za-z_][A-Za-z0-9_]*)\s+in\s+(?<expr>.*?)\s*\]-->", true);
            AddToken(TemplateTokenType.ForEach, @"<!--\{\{\s*foreach\s+(?<iterator>[A-Za-z_][A-Za-z0-9_]*)\s+in\s+(?<expr>.*?)\s*\}\}-->", true);
            AddToken(TemplateTokenType.EndBlock, @"<!--\$\[\s*end(for|if)\s*\]-->", true);
            AddToken(TemplateTokenType.EndBlock, @"<!--\{\{\s*end(for|if)\s*\}\}-->", true);
            AddToken(TemplateTokenType.If, @"<!--\$\[\s*if\s+(?<expr>.*?)\s*\]-->", true);
            AddToken(TemplateTokenType.If, @"<!--\{\{\s*if\s+(?<expr>.*?)\s*\}\}-->", true);
            AddToken(TemplateTokenType.Else, @"<!--\$\[\s*else\s*\]-->", true);
            AddToken(TemplateTokenType.Else, @"<!--\{\{\s*else\s*\}\}-->", true);
            AddToken(TemplateTokenType.ElseIf, @"<!--\$\[\s*elseif\s+(?<expr>.*?)\s*\]-->", true);
            AddToken(TemplateTokenType.ElseIf, @"<!--\{\{\s*elseif\s+(?<expr>.*?)\s*\}\}-->", true);
            AddToken(TemplateTokenType.Expression, @"<!--\$\[\s*(?<kw>(?i:RENDER|INCLUDE))\s*(?<expr>.*?)\s*\]-->", TokenID.Include);
            AddToken(TemplateTokenType.Expression, @"<!--\{\{\s*(?<kw>(?i:RENDER|INCLUDE))\s*(?<expr>.*?)\s*\}\}-->", TokenID.Include);
            AddToken(TemplateTokenType.Expression, @"\$\[\s*(?<kw>(?i:RENDER|INCLUDE))\s*(?<expr>.*?)\s*\]", TokenID.Include);
            AddToken(TemplateTokenType.Expression, @"\{\{\s*(?<kw>(?i:RENDER|INCLUDE))\s*(?<expr>.*?)\s*\}\}", TokenID.Include);
            AddToken(TemplateTokenType.Expression, @"\{\{\s*(?i:COMPONENT)\s*(?<expr>.*?)\s*\}\}", TokenID.Component);
            AddToken(TemplateTokenType.Expression, @"\{%\s*(?<expr>.*?)\s*%\}", TokenID.Component);
            AddToken(TemplateTokenType.Expression, @"\$\[\[\s*(?<expr>.*?)\s*(:(?<className>[^:]*?)\s*(:(?<classError>[^:]*?)\s*)?)?\]\]", TokenID.Control);
            AddToken(TemplateTokenType.Expression, @"\[\[\s*(?<expr>.*?)\s*(:(?<className>[^:]*?)\s*(:(?<classError>[^:]*?)\s*)?)?\]\]", TokenID.Control);
            AddToken(TemplateTokenType.Expression, @"<!--\{\{\s*(?<expr>.*?)\s*\}\}-->", true, TokenID.QuietExpression);
            AddToken(TemplateTokenType.Expression, @"\{\{\s*(?<expr>.*?)\s*(?:\`\s*(?<fmt>[^""]*?)\s*)?\}\}", TokenID.Expression);
            AddToken(TemplateTokenType.Expression, @"<!--\$\[\s*(?<expr>.*?)\s*\]-->", true, TokenID.QuietExpression);
            AddToken(TemplateTokenType.Expression, @"\$\[\s*(?<expr>.*?)\s*(?:\:\s*(?<fmt>[^""\:]*?)\s*)?\]", TokenID.Expression);
            AddToken(TemplateTokenType.Expression, @"\$\{\s*(?<tag>.*?)\s*(?<params>\(.+\))?\s*\}" , TokenID.Translation);
            AddToken(TemplateTokenType.Expression, @"##\s*(?<tag>.*?)\s*(?<params>\(.+\))?\s*##", TokenID.Translation);


            //\s*(?<expr>.*?)\s*(?:\:\s*(?<fmt>.*?)\s*)?
            //\s*(?<expr>.*?)\s*(?:\:\s*(?<fmt>.*?)\s*)?
        }

        protected override string OnEvalExpression(ExpressionParser parser, TokenMatch tokenMatch, IParserContext context)
        {
            TemplateParserContext parserContext = (TemplateParserContext)context;

            switch (tokenMatch.TokenId)
            {
                case TokenID.Control: return EvalControlExpression(parser, tokenMatch, parserContext);
                case TokenID.Include: return EvalIncludeExpression(parser, tokenMatch, parserContext);
                case TokenID.Component: return EvalComponentExpression(parser, tokenMatch, parserContext);
                case TokenID.Translation: return EvalTranslationExpression(parser, tokenMatch, parserContext);
                case TokenID.Expression: return EvalExpression(parser, tokenMatch, parserContext, false);
                case TokenID.QuietExpression: return EvalExpression(parser, tokenMatch, parserContext, true);
            }

            return "";
        }

        private string EvalControlExpression(ExpressionParser parser, TokenMatch match, TemplateParserContext context)
        {
            Control control = parser.Evaluate<Control>(match.SubMatches["expr"], context);

            string className = match.SubMatches.ContainsKey("className") ? match.SubMatches["className"] : null;
            string classError = match.SubMatches.ContainsKey("classError") ? match.SubMatches["classError"] : null;

            if (control != null)
            {
                string html = control.Render(context.ViewContext.View, className, classError);

                return context.ViewContext.View.ParseTranslations(html);
            }
            else
            {
                return "[?[" + match.SubMatches["expr"] + "]?]";
            }
        }

        private string EvalTranslationExpression(ExpressionParser parser, TokenMatch match, TemplateParserContext context)
        {
            string tag = match.SubMatches["tag"];
            string parameters = null;

            if (match.SubMatches.ContainsKey("params") && match.SubMatches["params"].Length > 0)
            {
                parameters = match.SubMatches["params"];
            }

            string translation = TranslationHelper.GetTranslation(context.ViewContext.View.ViewName, tag);

            if (translation == null)
                return "{?{" + tag + "}?}";

            if (parameters != null)
            {
                IParserContext localContext = context.CreateLocal();

                localContext.Set("__Translation__", translation);

                translation = parser.Evaluate<string>("string.Format(__Translation__," + parameters.Substring(1),localContext);
            }

            return translation;
        }

        private string EvalComponentExpression(ExpressionParser parser, TokenMatch match, TemplateParserContext context)
        {
            string expr = match.SubMatches["expr"];

            ParameterizedExpression pExpr = new ParameterizedExpression(expr);

            string componentName = pExpr.MainExpression;

            if (pExpr.MainExpression.StartsWith("(") && pExpr.MainExpression.EndsWith(")"))
            {
                componentName = parser.Evaluate<string>(pExpr.MainExpression, context);
            }

            TemplateParserContext newContext = new TemplateParserContext(context.ViewContext, context.Template);

            foreach (string varName in pExpr.Parameters.Keys)
                newContext.SetLocal(varName, parser.Evaluate(pExpr.Parameters[varName], context));

            return WebAppHelper.RunViewComponent(WebAppHelper.GetViewComponent(componentName), newContext);
        }

        private string EvalIncludeExpression(ExpressionParser parser, TokenMatch match, TemplateParserContext context)
        {
            string expr = match.SubMatches["expr"];

            ParameterizedExpression pExpr = new ParameterizedExpression(expr);

            string templateName = parser.Evaluate<string>(pExpr.MainExpression, context);

            string keyword = match.SubMatches["kw"].ToUpper();

            if (keyword == "RENDER")
            {
                TemplateParserContext newContext = new TemplateParserContext(context.ViewContext,context.Template);

                foreach (string varName in pExpr.Parameters.Keys)
                    newContext.Set(varName, parser.Evaluate(pExpr.Parameters[varName], context));

                return context.Template.RenderSubTemplate(templateName, newContext);
            }
            else
            {
                return context.Template.ReadSubTemplate(templateName);
            }
        }

        private string EvalExpression(ExpressionParser parser, TokenMatch tokenMatch, TemplateParserContext context, bool quiet)
        {
            IValueWithType result = parser.Evaluate(tokenMatch.SubMatches["expr"], context);

            if (quiet)
                return "";

            if (result.Value is Control)
            {
                return context.ViewContext.View.ParseTranslations(((Control)result.Value).Render(context.ViewContext.View));
            }

            if (result.Type == typeof(string) && result.Value != null)
                result.Value = context.ViewContext.View.ParseTranslations((string)result.Value);

            string fmt = tokenMatch.SubMatches.ContainsKey("fmt") ? tokenMatch.SubMatches["fmt"] : null;

            if (fmt != null)
                return String.Format("{0:" + fmt + "}", result.Value);
            else if (result.Value is string)
                return (string) result.Value;
            else
                return result.Value == null ? "" : Convert.ToString(result.Value,NumberFormatInfo.InvariantInfo);
        }

        private class ParameterizedExpression
        {
            private static readonly object _cacheLock = new object();
            private static readonly Dictionary<string,ParameterizedExpression> _cache = new Dictionary<string, ParameterizedExpression>();

            public readonly string MainExpression;
            public readonly Dictionary<string, string> Parameters;

            public ParameterizedExpression(string expression)
            {
                lock (_cacheLock)
                {
                    ParameterizedExpression cached;

                    if (_cache.TryGetValue(expression, out cached))
                    {
                        MainExpression = cached.MainExpression;
                        Parameters = cached.Parameters;
                        return;
                    }
                }

                Parameters = new Dictionary<string, string>();
    
                MatchCollection matches = Regex.Matches(expression, @"[,\s]*@(?<varname>[a-zA-Z_][a-zA-Z_0-9]*)\s*=");

                int index = -1;
                string varName = null;

                if (matches.Count < 1)
                {
                    MainExpression = expression;
                    return;
                }

                foreach (Match match in matches)
                {
                    if (index == -1)
                    {
                        MainExpression = expression.Substring(0, match.Index).Trim();
                    }
                    else
                    {
                        if (varName != null)
                            Parameters[varName] = expression.Substring(index, match.Index - index).Trim();
                    }

                    varName = match.Groups["varname"].Value;

                    index = match.Index + match.Length;
                }

                if (varName != null)
                    Parameters[varName] = expression.Substring(index, expression.Length - index).Trim();

                lock (_cacheLock)
                {
                    _cache[expression] = this;
                }
            }
        }

    }
}